/*
 * MIT License
 *
 * Copyright (c) 2020 wen.gu <454727014@qq.com>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

 /***************************************************************************
 * Name: ngl_message_parser2.c
 *
 * Purpose: implementation a parser2 to parse received data and output message instance
 *
 * Developer:
 *   wen.gu , 2023-04-09
 *
 * TODO:
 *
 ***************************************************************************/

#include <stdio.h>

#include "ngl/ngl_message_parser2.h"
#include "ngl/ngl_message.h"
#include "ngl/ngl_protocol.h"

/***************************************************************************
 * inner function  implementation
 ***************************************************************************/
static const uint8_t* parser2_on_parse_start(ngl_message_parser2_t* parser2, const uint8_t* data_ptr, const uint8_t* end_ptr) {
    while (data_ptr < end_ptr) {
        if (*data_ptr == NGL_MSG_SYNC_CODE) {
            data_ptr++;
            parser2->header_read_pos = 0;
            parser2->parse_state = NGL_MsgParser2StateBaseHeader;
            return data_ptr;
        }

        data_ptr++;
        
    }/** is left data not enough( size < NGL_MSG_SYNC_CODE_LEN), then skip left data */

    return end_ptr;
}

static const uint8_t* parser2_on_parse_base_header(ngl_message_parser2_t* parser2,  const uint8_t* data_ptr, const uint8_t* end_ptr) {
    if (parser2->header_read_pos < sizeof(ngl_base_header_t)) {    
        int32_t size = (int32_t)(end_ptr - data_ptr);    
        int32_t cpSize = sizeof(ngl_base_header_t) - parser2->header_read_pos;
        cpSize = (cpSize <= size) ? cpSize : size;
        memcpy(&(parser2->base_header_buf[parser2->header_read_pos]), data_ptr, cpSize);
        parser2->header_read_pos += cpSize;
        data_ptr += cpSize;
        size -= cpSize;
    }

    if (parser2->header_read_pos == sizeof(ngl_base_header_t)) {/** parse header data */
        ngl_base_header_t* base_hdr = (ngl_base_header_t*)parser2->base_header_buf;

        if (base_hdr->mlen < (base_hdr->elen + sizeof(ngl_base_header_t))) {/** error length */
            parser2->parse_state = NGL_MsgParser2StateSyncStart;
            return data_ptr; /** todo refine me?? */
        }

        if (!NGL_IS_HTYP_VER_MATCH(base_hdr->htyp)) { /** version mismatch */
            parser2->parse_state = NGL_MsgParser2StateSyncStart;
            return data_ptr; /** todo refine me?? */           
        }

        if (base_hdr->mlen > sizeof(ngl_base_header_t)) {
            if (base_hdr->elen > 0) {
                if (!NGL_HTYP_HAS_EXT_HDR(base_hdr->htyp)) { /** if extension header length > 0, but haven't extension header field, then is illegal */
                    parser2->parse_state = NGL_MsgParser2StateSyncStart;
                    return data_ptr; /** todo refine me?? */                    
                }

                parser2->ext_hdr_read_pos = 0;
                parser2->ext_hdr_size = base_hdr->elen;
                parser2->parse_state = NGL_MsgParser2StateExtensionHeader; 
            } else {
                parser2->parse_state = NGL_MsgParser2StatePayload;
            }

            parser2->payload_read_pos = 0;
            parser2->payload_size = base_hdr->mlen - sizeof(ngl_base_header_t) - base_hdr->elen;
            parser2->msg = ngl_message_create(base_hdr);

            if (!parser2->msg) {
                parser2->parse_state = NGL_MsgParser2StateSyncStart;
                return data_ptr; /** todo refine me?? */                   
            }
           
        } else {/** this case is: length == sizeof(ngl_base_header_t) */
            parser2->parse_state = NGL_MsgParser2StateEnd;
        }
    }

    return data_ptr;
}

static const uint8_t* parser2_on_parse_extension_header(ngl_message_parser2_t* parser2, const uint8_t* data_ptr, const uint8_t* end_ptr) {
    if (parser2->ext_hdr_read_pos < parser2->ext_hdr_size) {
        int32_t size = (int32_t)(end_ptr - data_ptr);
        int32_t cpSize = parser2->ext_hdr_size - parser2->ext_hdr_read_pos;
        cpSize = (cpSize <= size) ? cpSize : size;
        memcpy(ngl_message_get_extension_header_buffer(parser2->msg) + parser2->ext_hdr_read_pos, data_ptr, cpSize);
        parser2->ext_hdr_read_pos += cpSize;
        data_ptr += cpSize;
        size -= cpSize;
    }

    if (parser2->ext_hdr_read_pos == parser2->ext_hdr_size) {/** parse header data */
        parser2->parse_state = NGL_MsgParser2StatePayload;     //todo something
    }

    return data_ptr;
}


static const uint8_t* parser2_on_parse_payload(ngl_message_parser2_t* parser2, const uint8_t* data_ptr, const uint8_t* end_ptr) {
    if (parser2->payload_read_pos < parser2->payload_size) {   
        int32_t size = (int32_t)(end_ptr - data_ptr);
        int32_t cpSize = (int32_t)(parser2->payload_size - parser2->payload_read_pos);
        cpSize = (cpSize <= size) ? cpSize : size;
        memcpy(ngl_message_payload_buffer(parser2->msg) + parser2->payload_read_pos, data_ptr, cpSize);
        parser2->payload_read_pos += cpSize;
        data_ptr += cpSize;
    }

    if (parser2->payload_read_pos == parser2->payload_size) {
        parser2->parse_state = NGL_MsgParser2StateEnd;
    }

    return data_ptr;
}

static const uint8_t* parser2_on_parse_end(ngl_message_parser2_t* parser2, const uint8_t* data_ptr, const uint8_t* end_ptr) {
    //LOGD("===>: size: %d\n", size);
    if (*data_ptr == NGL_MSG_END_CODE) {
        data_ptr++;
        parser2->is_got_msg = NGL_TRUE;
    }
    //LOGD("===>: size: %d\n", size);

    parser2->parse_state = NGL_MsgParser2StateSyncStart;
    return data_ptr;
}

/***************************************************************************
 * API  implementation
 ***************************************************************************/

ngl_error_t ngl_msg_parser2_initialize(ngl_message_parser2_t* parser2) {
    if (!parser2) {
        return NGL_ErrInvalidArgument;
    }

    memset(parser2, 0, sizeof(ngl_message_parser2_t));
    parser2->parse_state = NGL_MsgParser2StateSyncStart;
    parser2->is_got_msg = NGL_FALSE;

    return NGL_ErrOK;
}

ngl_error_t ngl_msg_parser2_deinitialize(ngl_message_parser2_t* parser2) {
    if (parser2) {
        if (parser2->msg) {
            ngl_message_destroy(parser2->msg);
            parser2->msg = NULL;
        }
        return NGL_ErrOK;
    }

    return NGL_ErrInvalidArgument;
}

ngl_error_t ngl_msg_parser2_reset(ngl_message_parser2_t* parser2) {
    if (!parser2) {
        return NGL_ErrInvalidArgument;
    }

    if (parser2->msg) {
        ngl_message_destroy(parser2->msg);
        parser2->msg = NULL;
    }

    memset(parser2, 0, sizeof(ngl_message_parser2_t));
    parser2->parse_state = NGL_MsgParser2StateSyncStart;
    parser2->is_got_msg = NGL_FALSE;  

    return NGL_ErrOK;
}




const uint8_t* ngl_msg_parser2_fill_data(ngl_message_parser2_t* parser2, ngl_message_t** msg, const uint8_t* data, const uint8_t* end_ptr) {
    if (!parser2 || !data || !msg) {
        return data;
    }

    const uint8_t* data_ptr = data;

    //LOGD("===>: size: %d\n", size);
    while (data_ptr < end_ptr) {
        switch (parser2->parse_state) {
        case NGL_MsgParser2StateSyncStart:
            //LOGD("===>: size: %d, start: 0x%x\n", size, *data_ptr);
            data_ptr = parser2_on_parse_start(parser2, data_ptr, end_ptr);
            break;
        case NGL_MsgParser2StateBaseHeader:
            //LOGD("===>: size: %d, start: 0x%x\n", size, *data_ptr);
            data_ptr = parser2_on_parse_base_header(parser2, data_ptr, end_ptr);
            break; 
        case NGL_MsgParser2StateExtensionHeader:
            data_ptr = parser2_on_parse_extension_header(parser2, data_ptr, end_ptr);
            break;
        case NGL_MsgParser2StatePayload:
            data_ptr = parser2_on_parse_payload(parser2, data_ptr, end_ptr);
            //LOGD("===>: size: %d\n", size);
            break;
        case NGL_MsgParser2StateEnd:
            data_ptr = parser2_on_parse_end(parser2, data_ptr, end_ptr);

            if (parser2->is_got_msg == NGL_TRUE) {
                *msg = parser2->msg;
                parser2->is_got_msg = NGL_FALSE;                
                parser2->msg = NULL;
                return data_ptr;
            }
            //LOGD("===>: size: %d\n", size);
            break;
        default:
            //LOGD("===>: size: %d\n", size);
            parser2->parse_state = NGL_MsgParser2StateSyncStart; //start to parser next message, todo refine me?
            break;
        }
    }

    return data_ptr; /** todo, refineme */
    //LOGD("===>: size: %d\n", size);  
}
